<?php

abstract class AFramework
{
	protected	$config;
	protected 	$ok = true;
	protected 	$view;
	protected 	$guimode;
	protected 	$user;
	protected 	$controller;
	protected 	$realm;

	protected 	$messages = array();
	protected 	$events = array();

	const FRAMEWORK_MISSING_DSN = "Missing database DSN.";
	const FRAMEWORK_MISSING_CONFIG = "Missing platform config.";
	const FRAMEWORK_INVALID_TYPE = "Invalid object supplied to %s. Expected '%s' but got '%s'.";
	const FRAMEWORK_INVALID_ID = "Invalid ID supplied to %s. Expected a %s ID.";
	const FRAMEWORK_RECORD_NOT_FOUND = "The %s(s) you asked for were not found.";

	abstract public function load();
	abstract public function loadController($in_controller_template_pair);

	public function __construct()
	{
		$this->parent_pid = getmypid();
	}

	public function __destruct()
	{
		if ($this->parent_pid == getmypid())
		{
			DBConn::destroy();
		}
		else
		{
			DBConn::destroy(getmypid());
		}
	}

	/**
	 * @description Check the health of the instance
	 *
	 * This method will return the state of the object, that being defined as the
	 * AND() of the boolean results of all critical set-up methods.
	 *
	 * @return boolean health of the instance
	 */
	public function ok()
	{
		return $this->ok;
	}

	public function getRealm()
	{
		return $this->realm;
	}

	/**
	 * @description Gets a reference to the template engine
	 *
	 * @return Savant3 instance reference
	 */
	public function &view()
	{
		return $this->view;
	}

	/**
	 * @description Gets a reference to the logged in user
	 *
	 * @return User instance reference
	 */
	public function &user(&$in_user=false)
	{
		if ($in_user instanceof User)
			$this->user = $in_user;
		return $this->user;
	}

	/**
	 * @description Logs a message to disk
	 *
	 * @return nothing
	 */
	public function log_message($message, $path="data/")
	{
		$split_path = explode(".", $path);
		if(count($split_path) != 1)
		{
			// No file provided
			$path .= "application_log.txt";
		}
		// Write to that file
		if(!file_exists($path))
		{
			// Take out the filename
			$folder_path = explode("/", $path);
			array_pop($folder_path);
			$folder_path = implode("/", $folder_path);
			// Make the folder
			if(!$this->prepareFolder($folder_path))
			{
				die("can't create log file folder structure at $path");
			}
			// Make the file
			$fh = fopen($path, 'w') or die("can't create log file at $path");
			fclose($ourFileHandle);
		}

		$fh = fopen($path, 'a') or die("Can't open log file at $path");
		fwrite($fh, $message);
		fclose($fh);
	}

	public function getRequestedController()
	{
		return $this->controller;
	}

	/**
	 * @description Check if we are in the given method
	 *
	 * Check if $_POST['method']/$_GET['method'] is set to $in_method. Return answer.
	 *
	 * @param $in_method string to compare to $_POST['method']/$_GET['method']
	 * @return boolean whether or not we are in the given method
	 */
	public static function method($in_method)
	{
		if (!empty($_POST['method']) && $_POST['method'] == $in_method)
		{
			return true;
		}
		if (!empty($_GET['method']) && $_GET['method'] == $in_method)
		{
			return true;
		}
		return false;
	}

	public static function pathSanitize($in_path)
	{
		return str_replace('../','',$in_path);
	}

	/**
	 * @description Retrieve a reference to the superclass instance
	 *
	 * This static method returns a reference to the global instance of the platform.
	 * This allows other, unrelated instances of other objects to access instance methods
	 * on the platform.
	 *
	 * @return AFramework
	 */
	public static function &get($in_auto_create=false)
	{
		foreach ($GLOBALS as $global_var_name => &$global_var)
		{
			if ($global_var instanceof AFramework)
			{
				return $global_var;
			}
		}
		//if ($in_auto_create) { return new AFramework(); }
		return NULL;
	}

	/**
	 * @description Gets a configuration parameter
	 *
	 * Looks at the platform's in-mmeory copy of the config.ini file for the
	 * configuration pair requested. Returns the data or null of not found.
	 *
	 * A configuration pair describes the path to the data. Having for format:
	 *
	 * 	topic variable
	 *
	 * it points at data located here:
	 *
	 * 	[topic]
	 * 	variable=data
	 *
	 * @param area string space seperated configuration pair
	 * @return mixed vonfiguration data or null on failure
	 */
	public function getConfig($area)
	{
		$addr = explode(" ",$area); $as = sizeof($addr);
		$topic = array_shift($addr);
		$index = implode(" ",$addr);

		if ($as == 1)
		{
			if (isset($this->config[$topic]))
				return $this->config[$topic];
		}
		elseif (isset($this->config[$topic][$index]))
		{
			return $this->config[$topic][$index];
		}
		return NULL;
	}

	/**
	* @description Get the current GuiMode (ajax / frame)
	*
	* @return current gui mode
	*/
	public function getGuiMode()
	{
		return $this->guimode;
	}

	/**
	* @description Set the current GuiMode (ajax / frame)
	*
	* @return new guimode
	*/
	public function setGuiMode($guimode)
	{
		return $this->guimode = $guimode;
	}

	/**
	 * @description Queue a message to be displayed
	 *
	 * @param $message_text the message text
	 * @return nothing
	 */
	public function addMessage($in_msg_type, $in_message_text)
	{

		if (empty($this->messages[$in_msg_type]))
			$this->messages[$in_msg_type] = array();
		array_push(
			$this->messages[$in_msg_type],
			$in_message_text
		);
	}
	/**
	 * @description Purges the queue of messages
	 *
	 * @return nothing
	 */
	public function clearMessages($in_msg_type) { $this->messages[$in_msg_type] = array(); }
	/**
	 * @description Gets the list of messages currently waiting to be displayed
	 *
	 * @return array of messages
	 */
	public function getMessages($in_msg_type) { return ((!empty($this->messages[$in_msg_type])) ? $this->messages[$in_msg_type] : array()); }

	/**
	 * @description Loads the Framework config file
	 *
	 * Parses the CORE_CONFIG file into memory and saves it into the platform object.
	 *
	 * @see AFramework::getConfig()
	 * @return nothing
	 */
	protected function loadConfig($config_file)
	{
		if (!$this->ok()) return;
		$this->rid = mt_rand(0,10000);

		if (!file_exists($config_file))
		{
			$this->ok = false;
			die(AFramework::FRAMEWORK_MISSING_CONFIG);
		}

		$ini_array = parse_ini_file($config_file, true);
		$this->config = $ini_array;
	}

	/**
	* @description Adds a callback event to a given event's stack
	*
	* @param $in_event_name name of event to trigger on
	* @param $in_callback valid PHP callback
	*/
	public function addEvent($in_event_name, $in_callback)
	{
		if (empty($this->events[$in_event_name])) $this->events[$in_event_name] = array();
		$this->events[$in_event_name][] = $in_callback;
	}

	/**
	* @description Execute all callbacks associated with the given event
	*
	* @param @in_event_name event to trigger
	* @return null
	*/
	public function fireEvent($in_event_name)
	{
		if (empty($this->events)) return;
		if (empty($this->events[$in_event_name])) return;

		foreach ($this->events[$in_event_name] as $callback)
		{
			$params = func_get_args();
			if (is_array($params))
				array_shift($params);
			if (!is_array($params))
				$params = array();

			call_user_func_array($callback,$params);
		}
	}

	/**
	 * @description Queue a file to be included in the HTML
	 *
	 * Allows modules to load their own scripts and css in the head of the final document.
	 *
	 * @param string $resource url to the included file
	 * @param string $resource_type type of include. "script" or "css"
	 *
	 * @return nothing
	 */
	public function addInclude($resource,$resource_type)
	{
		if (!isset($this->view()->includes) || !is_array($this->view()->includes)) $this->view()->assign("includes",array());
		if (!isset($this->view()->raw_includes) || !is_array($this->view()->raw_includes)) $this->view()->assign("raw_includes",array());

		switch ($resource_type)
		{
			case "css":
				$wrapped_resource = "<link type=\"text/css\" rel=\"stylesheet\" media=\"screen\" href=\"$resource\" />";
				break;

			case "script":
				$wrapped_resource = "<script type=\"text/javascript\" src=\"$resource\"></script>";
				break;
		}

		array_push(
			$this->view()->includes,
			$wrapped_resource
		);

		$this->view()->raw_includes[$resource] = array($resource,$resource_type);
	}

	/**
	 * @description Shortens a string intelligently
	 *
	 * This method tries to get a short version of the given string, based on a given number
	 * of words or characters. It tries to prevent cutting off words in the middle, and adds
	 * elipsis at the end if needed.
	 *
	 * @param $in_string the string to be shortened
	 * @param $in_shorten_type the type of shorten to perform. "char" or "word"
	 * @param $in_shorten_size the desired length of string.
	 *
	 * @return string shortened version of the string
	 */
	public static function getShortString($in_string, $in_shorten_type="char", $in_shorten_size=100)
	{
		switch ($in_shorten_type)
		{
			case "char":
				$strlen = strlen($in_string);
				if ($strlen <= $in_shorten_size) { return $in_string; }

				$i = 0; $substr_pos = 0; $test_substr_pos = -1;
				while ($test_substr_pos < $strlen)
				{
					$i++;
					$test_substr_pos = strpos($in_string," ",$test_substr_pos+1);
					if ($test_substr_pos === false || $test_substr_pos > $in_shorten_size || $test_substr_pos == 0) break;
					$substr_pos = $test_substr_pos;
				}
				if ($substr_pos === 0) $substr_pos = $in_shorten_size;
				$substr = substr($in_string,0,$substr_pos);
				$substr .= (($strlen > $substr_pos) ? "..." : "");
				break;
			case "word":
				$words = explode(" ",$in_string);
				if (count($words) <= $in_shorten_size) { return $in_string; }

				$new_words = array_slice($words,0,$in_shorten_size);
				$substr = implode(" ",$new_words);
				$substr .= (count($words) > count($new_words)) ? "..." : "";
				break;
		}
		return $substr;
	}

	/**
	* @description Tries to recursively create a given linear tree structure
	*
	* @param $folderpath desired/filesystem/path/
	* @return boolean status of operation
	*/
	public static function prepareFolder($folderpath)
	{
		if (file_exists($folderpath)) return true;
		$folder_array = explode("/",$folderpath);
		for ($i = 1; $i <= count($folder_array); $i++)
		{
			$test_folder = implode("/",array_slice($folder_array,0,$i));
			if($test_folder != "..")
			{
				if (!strlen($test_folder)) continue;
				if (file_exists($test_folder)) continue;
				@mkdir($test_folder);
				if (!file_exists($test_folder))
					echo "failed to create $test_folder<br/>\n";
			}
		}
		if (file_exists($folderpath))
		{
			return true;
		}
		else
		{
			return false;
		}
	}

	/**
	* @description Recursively delete folder contents
	*
	* Optionally deletes target folder also
	*
	* @param $in_dir folder to clean out
	* @param $in_purge_root optionally erase target folder itself. defaults to true
	* @return boolean status of operation
	*/
	public static function purgeFolder($in_dir, $in_purge_root=true, $in_exception_list=array())
	{
		if(!$dh = @opendir($in_dir)) return;
		while ($obj = readdir($dh))
		{
			if ($obj == '.' || $obj == '..')
				continue;
			if (!self::isExceptedFile($obj,$in_exception_list))
			{
				if (!@unlink($in_dir.'/'.$obj))
					self::purgeFolder($in_dir.'/'.$obj, true, $in_exception_list);
			}
		}
		if ($in_purge_root)
		{
			closedir($dh);
			@rmdir($in_dir);
		}
	}

	private static function isExceptedFile($file,$exception_list)
	{
		foreach ($exception_list as $exception)
		{
			if (substr($exception,0,1) == '!')
			{
				$res = preg_match(substr($exception,1),strtolower($file));
				if ($res) return true;
			}
			else
			{
				$res = (strtolower($file) == strtolower($exception));
				if ($res) return true;
			}
		}
		return false;
	}

	/**
	* @description Returns the ordinal suffix of a number
	*
	* e.g. 1st 2nd 3rd 4th
	*
	* @param $in_number number for which to generate ordinal
	* @return string ordinal
	*/
	public static function getOrdinal($in_number)
	{
		$suffix = substr($in_number, -1);
		$extended_suffix = substr($in_number, -2);
		if ($extended_suffix == 11 || $extended_suffix == 12 || $extended_suffix == 13)
		{
			return "th";
		}

		switch ($suffix)
		{
			case 1: return "st";
			case 2: return "nd";
			case 3: return "rd";
		}

		return "th";
	}

	/**
	* @description Using the provided amount, return the plural-safe form of a given noun
	*
	* @param $in_string noun to pluralize safely
	* @param $in_count number of <noun>(s)
	* @return pluralized version of $in_string
	*/
	public static function pluralize($in_string,$in_count)
	{
		$char = "s";
		if ($in_string{-1} == "s") $char = "'";
		if ($in_string{-1} == "x") $char = "es";
		return $in_string.(($in_count == 1) ? "" : $char);
	}

	/**
	* @description Convert integer timestamp to short date string
	*
	* Converts an integer unix epoch timestamp into its MMMM dd, YYYY format
	*
	* @param $in_date_timestamp unix timestamp to convert
	* @return  MMMM dd, YYYY format
	*/
	public static function dateToString($in_date_timestamp)
	{
		return date("F d, Y",$in_date_timestamp);
	}

	/**
	* @description Convert integer timestamp to long date string with time
	*
	* Converts an integer unix epoch timestamp into:
	*
	* 	April 10, 2007 at 5:37 PM
	*
	* @param $in_date_timestamp unix timestamp to convert
	* @return  MMMM dd, YYYY 'at' hh:mm 'AM/PM' format
	*/
	public static function dateToFullString($in_date_timestamp)
	{
		return date("F d, Y",$in_date_timestamp)." at ".date("g:i A",$in_date_timestamp);
	}

	/**
	* @description Convert a friendly time into seconds
	*
	* Would take '60m' and give '3600'
	*
	* Supports up to weeks
	*
	* @param $in_time friendly time to convert
	* @return $in_time in seconds
	*/
	public static function timeToSeconds($in_time)
	{
		$parsed_time = explode(",",trim(str_replace(
			array('s','m','h','d','y'),
			array('*1,','*60,','*3600,','*86400,','*604800,'),
			$in_time
		),' ,'));

		$parsed_time = implode('+',$parsed_time);

		eval("\$final_time = $parsed_time;");
		return $final_time;
	}

	/**
	* @description Intelligently get the PHP type of an object
	*
	* Tries to figure out the native type, or custom object name of the given variable
	*
	* @param &$var variable to check
	* @return Internal PHP type
	*/
	public static function gettype(&$var)
	{
		$pre_type = gettype($var);
		if ($pre_type == "object")
		{
			return get_class($var);
		}
		return $pre_type;
	}

	public function makeGarbage($amount=8)
	{
		$possible = '!@#%^&*()_+{}|:<>?,./;[]-=`~1234567890qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM';
		$garbage = '';
		$i = 0;
		while ($i < $amount)
		{
			$garbage .= substr($possible, mt_rand(0, strlen($possible)-1), 1);
			$i++;
		}
		return $garbage;
	}

	public static function debug($message="")
	{
		if ($message) { $GLOBALS['debug_panel'][] = $message; }

		if (DEBUG_LOUD)
		{
			if ($message != "") {
				echo "<div style=\"border:1px solid #ff9c00;background-color:#ffffe9;color:#ff9c00;padding:5px;font-weight:bold;margin:3px 0px;margin-left:20px;\">".$message."</div>\n";
				return;
			}
		}
	}

	public function toString()
	{
		return "AFramework (rid: #".$this->rid.") (pid: #".$this->parent_pid."\n";
	}

}

class FrameworkInvalidIDException extends Exception
{
	function __construct($in_method,$in_expected_type)
	{
		parent::__construct(sprintf(AFramework::FRAMEWORK_INVALID_ID,$in_method,$in_expected_type));
	}
}

class FrameworkRecordNotFoundException extends Exception
{
	function __construct($in_type)
	{
		parent::__construct(sprintf(AFramework::FRAMEWORK_RECORD_NOT_FOUND,$in_type));
	}
}

class FrameworkRecordAlreadyExistsException extends Exception
{
	function __construct($in_type)
	{
		parent::__construct(sprintf(AFramework::FRAMEWORK_RECORD_NOT_FOUND,$in_type));
	}
}
class FrameworkInvalidTypeException extends Exception
{
	function __construct($in_method,$in_expected_type,$in_received_type="undefined")
	{
		parent::__construct(sprintf(AFramework::FRAMEWORK_INVALID_TYPE,$in_method,$in_expected_type,$in_received_type));
	}
}

class FrameworkServerInfoDoesNotExistException 			extends Exception { }
class FrameworkEmptyIDException							extends Exception { }

?>
